#ifndef AER_POSTEFFECT_HPP_
#define AER_POSTEFFECT_HPP_

#include "../aerDefs.hpp"
#include <typeinfo>
#include <string>
#include <vector>
#include <iostream>

// used by most post effects
#include "../device/aerTexture.hpp"
#include "../device/aerRenderTexture.hpp"
#include "../device/aerProgram.hpp"
#include "../misc/aerScreenQuad.hpp"


namespace aer {

/// ===============================================
///  Base class for post-process effects
///
///   +Inputs are pointers to external data
///   +Outputs are internal data
/// ===============================================
class PostEffect
{
  public:
    enum Target 
    {
      TEXTURE,
      SCREEN      
    };
    
  private:    
    /// I/O data pointer handler
    struct Data
    {
      void *ptr;
      std::string name;       // TODO : use semantics instead
      std::string typeName;
      
      template<typename T> Data(const std::string &name, T *ptr)
        : ptr(ptr), name(name), typeName(typeid(T).name())
      {}
    };
  
    typedef std::vector<Data>    DataArray;
    typedef DataArray::iterator DataIterator;
  
  
  private:
    Target m_target;
    DataArray m_inputData;    /// Kept pointer to external data (double pointer)
    DataArray m_outputData;   /// Kept pointer to internal data (simple pointer)
  
  
  protected:
    template<typename T>
    void addInput(const std::string &name, T &input) { 
      m_inputData.push_back( Data( name, &input) ); 
    }
    
    template<typename T>
    void addOutput(const std::string &name, T &output) { 
      m_outputData.push_back( Data( name, &output) ); 
    }
    
      
  public:
    PostEffect() : m_target(TEXTURE) {}
    virtual ~PostEffect() {}
    
    
    virtual void init() = 0;
    virtual void resize(const U32 width, const U32 height) = 0;    
    virtual void apply() = 0;
    
    
    /// Link an input to an external data
    template<typename T>
    bool linkInput( const std::string &name, T *pInput)
    {
      T **input = getInput<T>(name);
      
      if (!input) {
        return false;
      }
      
      *input = pInput;
      return true;
    }
    
    /// Returns a double pointer to an Input data location
    template<typename T>
    T** getInput(const std::string &name)
    {
      for (DataIterator it=m_inputData.begin(); it!=m_inputData.end(); ++it)
      {
        if (it->name != name) {
          continue;
        }
        
        const std::string &typeName = typeid(T*).name();
        if (it->typeName != typeName)
        {
          std::cerr << "PostEffect::getInput : Invalid input type for \"" << it->name
                    << "\". Expecting " << it->typeName 
                    << " instead of "   << typeName
                    << ".\n";
          return NULL;
        }
        
        return reinterpret_cast<T**>(it->ptr);
      }
          
      return NULL;
    }
    
    /// Returns a simple pointer to an output data 
    template<typename T>
    T* getOutput(const std::string &name)
    {
      for (DataIterator it=m_outputData.begin(); it!=m_outputData.end(); ++it)
      {
        if (it->name != name) {
          continue;
        }
        
        const std::string &typeName = typeid(T).name();
        if (it->typeName != typeName)
        {
          std::cerr << "PostEffect::getOutput : Invalid output type for \"" << it->name
                    << "\". Expecting " << it->typeName 
                    << " instead of "   << typeName 
                    << ".\n";
          return NULL;
        }
        
        return reinterpret_cast<T*>(it->ptr);
      }
      
      return NULL;
    }
    
    /// Link an effect outputs with matching inputs
    void linkEffect(PostEffect *effect)
    {
      DataIterator in, out;
      
      for (out=effect->m_outputData.begin(); out!=effect->m_outputData.end(); ++out)
      {
        for (in=m_inputData.begin(); in!=m_inputData.end(); ++in) 
        {
          if ((in->name == out->name)/* && (in->typeName == out->typeName)*/)
          {
            void **inPtr = (void**)in->ptr;
            *inPtr = out->ptr;
            break;
          }
        }
      }
    }
    

    Target getTarget() const { return m_target; }
    void setTarget(Target target) { m_target = target; }
};
  
} // aer

#endif //AER_POSTEFFECT_HPP_
